-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement a basic generic API client #313
Conversation
This is very helpful. At least from my side it makes way clearer how to design code here. The idea of request-like helper that has pagination, auth and other things built-in is good. api
on top of usual
the returned generator may be passed directly to ie.
where paginated.data is a generator function ie.
I'd strive move as much logic into a common code and make paginators and authenticators as simple as possible. For example paginator could get a last response and a prepared request and modify it to get next page. we could even make it more abstract and pass header, paramas etc.
To declare an endpoint we basically provide kwargs to the paginated_request. the declaration can also take default authenticator and paginator then in the Approach |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks cool, I also wonder if pagination strategy should also take params from .dlt/config.yml
?
sources/api_client.py
Outdated
def get(self, path="", params=None): | ||
return self.make_request(path, method="get", params=params) | ||
|
||
def post(self, path="", json=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe we can also use typing override to define signature variances?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sultaniman could you elaborate on this one?
tests/notion/test_notion_database.py
Outdated
@@ -3,7 +3,7 @@ | |||
from sources.notion.helpers.database import NotionDatabase | |||
from sources.notion.helpers.client import NotionClient | |||
|
|||
|
|||
@pytest.mark.skip |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Skipping this since it's testing the former client
@@ -21,7 +21,7 @@ def test_all_resources(destination_name: str) -> None: | |||
table_counts = load_table_counts(pipeline, *table_names) | |||
|
|||
assert table_counts["employees"] >= 31 | |||
assert table_counts["absence_types"] >= 6 | |||
assert table_counts["absence_types"] >= 5 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adjusting for a fresh Personio account.
sources/api_client.py
Outdated
self.base_url = base_url | ||
self.headers = headers | ||
self.auth = auth | ||
self.paginator = paginator if paginator else HeaderLinkPaginator() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what should be a default here, so I went with a personal preference.
sources/rest_api/typing.py
Outdated
) | ||
|
||
PaginatorConfigDict = Dict[str, Any] | ||
PaginatorType = Union[Any, BasePaginator, str, PaginatorConfigDict] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in ba46fda by dlt-hub/dlt#1082
Thanks again @sultaniman for the review!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO (for later)
-
"Endpoint" does not make it clear if single entity is returned or a list of entities. should we have "none" paginator to indicate that? or some boolean flag
-
What is the use of SinglePage paginator now? we have this
add_limit
which makes any paginator single page -
I should be able to bind same incremental to start/since param (start_value) and to end/before param (end_value).
-
I should be able to bind same transformer to many params when id is compound (this is less important)
-
paginators should be configspecs (dataclasses) so they can be configured and initialized like authentiators. we could use the same factory approach for both - I can do this one
-
RESTAPIConfig
should accept an instance of DltResource here to allow completely code to be mixed with declared endpoint resources
resources: List[Union[str, EndpointResource]]
(this is less important)
7. several types in typing.py must go to dlt core ASAP. I can also handle that. we could also generate typed dicts automatically for all our configspecs.
sources/rest_api/__init__.py
Outdated
param_name=param_name, | ||
field_path=resolved_param.resolve_config.field_path, | ||
): | ||
items = items or [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should allow for items
that are not lists and encapsulate them. also "items" that evaluate to False - that rather should not happen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Tell us what you do here
This PR implements a basic generic API client. Mostly by trying to generilize pagination and auth logic which many of the sources in this repo reimplement. The goal is to speed up sources development by unifying some common functionality. At the same time I'm trying to design the client's API to be as close to Requests as possible.
Prior relevant work: dlt-hub/dlt-init-openapi-demo#2
Example: Get issues GitHub resource from dlt docs.
Original
Using RESTClient